/**
 * Used to configure the global error handling function, which can monitor vue errors, script errors, static resource errors and Promise errors
 */

import type { App } from 'vue'
import type { ErrorLogInfo } from '@/types/store'

import { useErrorLogStoreWithOut } from '@/store/modules/errorLog'

import { ErrorTypeEnum } from '@/enums/exceptionEnum'
import projectSetting from '@/settings/projectSetting'

/**
 * Handling error stack information
 * @param error
 */
function processStackMsg(error: Error) {
  if (!error.stack)
    return ''

  let stack = error.stack
    .replace(/\n/gi, '') // Remove line breaks to save the size of the transmitted content
    .replace(/\bat\b/gi, '@') // At in chrome, @ in ff
    .split('@') // Split information with @
    .slice(0, 9) // The maximum stack length (Error.stackTraceLimit = 10), so only take the first 10
    .map(v => v.replace(/^\s*|\s*$/g, '')) // Remove extra spaces
    .join('~') // Manually add separators for later display
    .replace(/\?[^:]+/gi, '') // Remove redundant parameters of js file links (?x=1 and the like)
  const msg = error.toString()
  if (!stack.includes(msg))
    stack = `${msg}@${stack}`

  return stack
}

/**
 * get comp name
 * @param vm
 */
function formatComponentName(vm: any) {
  if (vm.$root === vm) {
    return {
      name: 'root',
      path: 'root',
    }
  }

  const options = vm.$options
  if (!options) {
    return {
      name: 'anonymous',
      path: 'anonymous',
    }
  }
  const name = options.name || options._componentTag
  return {
    name,
    path: options.__file,
  }
}

/**
 * Configure Vue error handling function
 */

function vueErrorHandler(err: unknown, vm: any, info: string) {
  const errorLogStore = useErrorLogStoreWithOut()
  const { name, path } = formatComponentName(vm)
  errorLogStore.addErrorLogInfo({
    type: ErrorTypeEnum.VUE,
    name,
    file: path,
    message: (err as Error).message,
    stack: processStackMsg(err as Error),
    detail: info,
    url: window.location.href,
  })
}

/**
 * Configure script error handling function
 */
export function scriptErrorHandler(event: Event | string, source?: string, lineno?: number, colno?: number, error?: Error) {
  if (event === 'Script error.' && !source)
    return false

  const errorInfo: Partial<ErrorLogInfo> = {}
  colno = colno || (window.event && (window.event as any).errorCharacter) || 0
  errorInfo.message = event as string
  if (error?.stack)
    errorInfo.stack = error.stack
  else
    errorInfo.stack = ''

  const name = source ? source.slice(source.lastIndexOf('/') + 1) : 'script'
  const errorLogStore = useErrorLogStoreWithOut()
  errorLogStore.addErrorLogInfo({
    type: ErrorTypeEnum.SCRIPT,
    name,
    file: source as string,
    detail: `lineno${lineno}`,
    url: window.location.href,
    ...(errorInfo as Pick<ErrorLogInfo, 'message' | 'stack'>),
  })
  return true
}

/**
 * Configure Promise error handling function
 */
function registerPromiseErrorHandler() {
  window.addEventListener(
    'unhandledrejection',
    (event) => {
      const errorLogStore = useErrorLogStoreWithOut()
      errorLogStore.addErrorLogInfo({
        type: ErrorTypeEnum.PROMISE,
        name: 'Promise Error!',
        file: 'none',
        detail: 'promise error!',
        url: window.location.href,
        stack: 'promise error!',
        message: event.reason,
      })
    },
    true,
  )
}

/**
 * Configure monitoring resource loading error handling function
 */
function registerResourceErrorHandler() {
  // Monitoring resource loading error(img,script,css,and jsonp)
  window.addEventListener(
    'error',
    (e: Event) => {
      const target = e.target ? e.target : (e.srcElement as any)
      const errorLogStore = useErrorLogStoreWithOut()
      errorLogStore.addErrorLogInfo({
        type: ErrorTypeEnum.RESOURCE,
        name: 'Resource Error!',
        file: (e.target || ({} as any)).currentSrc,
        detail: JSON.stringify({
          tagName: target.localName,
          html: target.outerHTML,
          type: e.type,
        }),
        url: window.location.href,
        stack: 'resource is not found',
        message: `${(e.target || ({} as any)).localName} is load error`,
      })
    },
    true,
  )
}

/**
 * Configure global error handling
 * @param app
 */
export function setupErrorHandle(app: App) {
  const { useErrorHandle } = projectSetting
  if (!useErrorHandle)
    return

  // Vue exception monitoring;
  app.config.errorHandler = vueErrorHandler

  // script error
  window.onerror = scriptErrorHandler

  //  promise exception
  registerPromiseErrorHandler()

  // Static resource exception
  registerResourceErrorHandler()
}
